#include "LightRendererGL.h"
#include "AppMacros.h"
#include "CCDrawingPrimitives.h"
#include "ShapeStyleDef.h"

USING_NS_CC;

LightRendererGL::LightRendererGL(){
	m_Color[0] = m_Color[1] = m_Color[2] = m_Color[3] = 1.0f;
	m_PointSize = 1.0f;
	m_Textures.push_back(NULL);
	m_MaskStack = 0;
	init();
}


void LightRendererGL::init()
{
	m_Shader.initWithVertexShaderFilename("Shaders/LightShader_vertex.glsl", "Shaders/LightShader_fragment.glsl");
	m_Shader.addAttribute(kCCAttributeNamePosition, kCCVertexAttrib_Position);
	m_Shader.addAttribute(kCCAttributeNameTexCoord, kCCVertexAttrib_TexCoords);

	m_Shader.link();
	m_Shader.updateUniforms();

	// shader choice
	u_ActiveShader					= glGetUniformLocation( m_Shader.getProgram(), "u_ActiveShader" );
	// simple color
	u_color							= glGetUniformLocation( m_Shader.getProgram(), "u_color" );
	// cx form
	u_use_cxform					= glGetUniformLocation( m_Shader.getProgram(), "u_use_cxform" );
	u_cxform_mult_color				= glGetUniformLocation( m_Shader.getProgram(), "u_cxform_mult_color" );
	u_cxform_add_color				= glGetUniformLocation( m_Shader.getProgram(), "u_cxform_add_color" );
	// gradient 
	u_ColorControl					= glGetUniformLocation( m_Shader.getProgram(), "u_ColorControl" );
	u_RatioControl					= glGetUniformLocation( m_Shader.getProgram(), "u_RatioControl" );
	u_isLinear						= glGetUniformLocation( m_Shader.getProgram(), "u_isLinear" );
}


void LightRendererGL::beginDraw()
{
	m_Shader.use();
}

void LightRendererGL::endDraw()
{

}


void LightRendererGL::pushMatrix( const MATRIX& transform ){

	kmGLPushMatrix();

	kmMat4 transfrom4x4;

    transfrom4x4.mat[2] = transfrom4x4.mat[3] = transfrom4x4.mat[6] = transfrom4x4.mat[7] = 
		transfrom4x4.mat[8] = transfrom4x4.mat[9] = transfrom4x4.mat[11] = transfrom4x4.mat[14] = 0.0f;

    transfrom4x4.mat[10] = transfrom4x4.mat[15] = 1.0f;

    transfrom4x4.mat[0] =		transform._11; 
	transfrom4x4.mat[4] =		transform._21; 
	transfrom4x4.mat[12] =		transform._13;
    transfrom4x4.mat[1] =		transform._12; 
	transfrom4x4.mat[5] =		transform._22; 
	transfrom4x4.mat[13] =		transform._23;


	kmGLMultMatrix( &transfrom4x4 );

	m_Shader.setUniformForModelViewProjectionMatrix();
}

void LightRendererGL::popMatrix(){
	kmGLPopMatrix();

	m_Shader.setUniformForModelViewProjectionMatrix();
}

void LightRendererGL::DrawLine( const vec2& origin, const vec2& destination ){

	ccVertex2F vertices[2] = {
		{origin.x, origin.y},
		{destination.x, destination.y}
	};

	glEnableVertexAttribArray( kCCVertexAttrib_Position );
	glDisableVertexAttribArray( kCCVertexAttrib_Color );
	glDisableVertexAttribArray( kCCVertexAttrib_TexCoords );

	glVertexAttribPointer(kCCVertexAttrib_Position, 2, GL_FLOAT, GL_FALSE, 0, vertices);
	
	glDrawArrays(GL_LINES, 0, 2);
}

void LightRendererGL::DrawQuadBezier( const vec2& origin, const vec2& control, const vec2& destination, uint32 segments ){
	//ccDrawQuadBezier( CCPoint(origin.x,origin.y), CCPoint(control.x,control.y) , CCPoint(destination.x,destination.y), segments );
}

void LightRendererGL::DrawPoint( const vec2& point ){
	//ccDrawCircle(CCPoint(point.x,point.y), m_PointSize, 0, 10 , false, 1, 1 );
}



void LightRendererGL::setCurrentColor( float r, float g, float b, float a ){
	
	m_Color[0] = r;
	m_Color[1] = g;
	m_Color[2] = b;
	m_Color[3] = a;

	m_Shader.setUniformLocationWith4fv(u_color, (GLfloat*) m_Color, 1);
}

void LightRendererGL::setPointSize( float pointSize ){
	m_PointSize = pointSize;
	glLineWidth( m_PointSize );
}

#define DEBUG_DRAW_TRIS_EDGE 1
#define MAX_TRIS_BUFFER 1000


void LightRendererGL::DrawSolidTris( const array<vec2>& Tris )
{
	/*
	if( Tris.size() < MAX_TRIS_BUFFER ){
		CCPoint vertices[MAX_TRIS_BUFFER];
		int size = 0;
		for (uint32 i=0; i<Tris.size(); i++){
			vertices[i].setPoint( Tris[i].x, Tris[i].y );
			size++;
		}
		ccDrawSolidTris( vertices, size, m_Color);

#if DEBUG_DRAW_TRIS_EDGE
		CCPoint verticesLine[MAX_TRIS_BUFFER*2];
		int sizeLine = 0;
		for (int32 i=0; i<size; i+=3){
			verticesLine[sizeLine] = vertices[i]; sizeLine++; verticesLine[sizeLine] = vertices[i+1]; sizeLine++;
			verticesLine[sizeLine] = vertices[i+1]; sizeLine++; verticesLine[sizeLine] = vertices[i+2]; sizeLine++;
			verticesLine[sizeLine] = vertices[i+2]; sizeLine++; verticesLine[sizeLine] = vertices[i]; sizeLine++;
		}
		setPointSize(2.0f);
		pushCXFORM( CXFORM(0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,1.0f), false );
		ccDrawLines( verticesLine, sizeLine,ccc4f(0.0f,0.0f,0.0f,1.0f) );
		popCXFORM();
		setPointSize(1.0f);
		pushCXFORM( CXFORM(0.0f,0.0f,0.0f,0.0f,1.0f,1.0f,1.0f,1.0f), false );
		ccDrawLines( verticesLine, sizeLine,ccc4f(0.0f,0.0f,0.0f,1.0f) );
		popCXFORM();
#endif
	}
	else {
		assert(0);
	}
	*/
}

void LightRendererGL::DrawSolidTrisTextured( const array<vec2>& Tris, const array<vec2>& UVs, uint32 texture ){

	/*
	CCTexture2D* pTexture = m_Textures[texture];

	if( Tris.size() < MAX_TRIS_BUFFER && UVs.size() < MAX_TRIS_BUFFER && pTexture ){
		CCPoint vertices[MAX_TRIS_BUFFER];
		CCPoint uvs[MAX_TRIS_BUFFER];
		int size = 0;
		for (uint32 i=0; i<Tris.size(); i++){
			vertices[i].setPoint( Tris[i].x, Tris[i].y );
			size++;
		}
		int size2 = 0;
		for (uint32 i=0; i<UVs.size(); i++){
			uvs[i].setPoint( UVs[i].x, UVs[i].y );
			size2++;
		}

		assert( size == size2 );

		ccDrawSolidTrisTextured( vertices, uvs, size, pTexture );

#if DEBUG_DRAW_TRIS_EDGE
		CCPoint verticesLine[MAX_TRIS_BUFFER*2];
		int sizeLine = 0;
		for (int32 i=0; i<size; i+=3){
			verticesLine[sizeLine] = vertices[i]; sizeLine++; verticesLine[sizeLine] = vertices[i+1]; sizeLine++;
			verticesLine[sizeLine] = vertices[i+1]; sizeLine++; verticesLine[sizeLine] = vertices[i+2]; sizeLine++;
			verticesLine[sizeLine] = vertices[i+2]; sizeLine++; verticesLine[sizeLine] = vertices[i]; sizeLine++;
		}
		setPointSize(2.0f);
		pushCXFORM( CXFORM(0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,1.0f), false );
		ccDrawLines( verticesLine, sizeLine,ccc4f(0.0f,0.0f,0.0f,1.0f) );
		popCXFORM();
		setPointSize(1.0f);
		pushCXFORM( CXFORM(0.0f,0.0f,0.0f,0.0f,1.0f,1.0f,1.0f,1.0f), false );
		ccDrawLines( verticesLine, sizeLine,ccc4f(0.0f,0.0f,0.0f,1.0f) );
		popCXFORM();
#endif
	}
	else {
		assert(0);
	}
	*/
}

void LightRendererGL::DrawSolidTrisGradient( const array<vec2>& Tris, const array<vec2>& UVs, const GRADIENT& Gradient ){

	/*
	if( Tris.size() < MAX_TRIS_BUFFER && UVs.size() < MAX_TRIS_BUFFER ){
		CCPoint vertices[MAX_TRIS_BUFFER];
		CCPoint uvs[MAX_TRIS_BUFFER];
		int size = 0;
		for (uint32 i=0; i<Tris.size(); i++){
			vertices[i].setPoint( Tris[i].x, Tris[i].y );
			size++;
		}
		int size2 = 0;
		for (uint32 i=0; i<UVs.size(); i++){
			uvs[i].setPoint( UVs[i].x, UVs[i].y );
			size2++;
		}

		assert( size == size2 );

		ccDrawSolidTrisGradient( vertices, uvs, size, Gradient.getColorControl(), Gradient.getRatioControl(), ! Gradient.m_isLinear );

#if DEBUG_DRAW_TRIS_EDGE
		CCPoint verticesLine[MAX_TRIS_BUFFER*2];
		int sizeLine = 0;
		for (int32 i=0; i<size; i+=3){
			verticesLine[sizeLine] = vertices[i]; sizeLine++; verticesLine[sizeLine] = vertices[i+1]; sizeLine++;
			verticesLine[sizeLine] = vertices[i+1]; sizeLine++; verticesLine[sizeLine] = vertices[i+2]; sizeLine++;
			verticesLine[sizeLine] = vertices[i+2]; sizeLine++; verticesLine[sizeLine] = vertices[i]; sizeLine++;
		}
		setPointSize(2.0f);
		pushCXFORM( CXFORM(0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,0.0f,1.0f), false );
		ccDrawLines( verticesLine, sizeLine,ccc4f(0.0f,0.0f,0.0f,1.0f) );
		popCXFORM();
		setPointSize(1.0f);
		pushCXFORM( CXFORM(0.0f,0.0f,0.0f,0.0f,1.0f,1.0f,1.0f,1.0f), false );
		ccDrawLines( verticesLine, sizeLine,ccc4f(0.0f,0.0f,0.0f,1.0f) );
		popCXFORM();
#endif
	}
	else {
		assert(0);
	}
	*/
}

uint32 LightRendererGL::createTextureFromRowData( uint8* _rowData, uint32 width, uint32 height ){

	CCImage image;                
	if (! image.initWithImageData((void*)_rowData, width*height*4 , CCImage::kFmtRawData, width, height ) ){
		assert(0); // error
	}

	CCTexture2D *pTexture = CCTextureCache::sharedTextureCache()->addUIImage( &image, NULL );
	if(pTexture){
		int index = m_Textures.size();
		m_Textures.push_back( pTexture );

		return index;
	}

	return 0;
}



void LightRendererGL::PushMask_Start(){
	
	if(m_MaskStack == 0){
		glClearStencil(0);
		glClearDepth(0);
		glClear(GL_STENCIL_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
		glEnable(GL_STENCIL_TEST);
		glEnable(GL_DEPTH_TEST);
	}
	
	glStencilFunc(GL_EQUAL,m_MaskStack,-1);
	glStencilOp(GL_KEEP,GL_INCR,GL_INCR);

	glDepthFunc(GL_NEVER);
}

void LightRendererGL::PushMask_End(){

	m_MaskStack++;
	glStencilFunc(GL_EQUAL,m_MaskStack,-1);
	glStencilOp(GL_KEEP,GL_KEEP,GL_KEEP);

	glDepthFunc(GL_ALWAYS);
}

void LightRendererGL::PopMask_Start(){

	glStencilFunc(GL_EQUAL,m_MaskStack,-1);
	glStencilOp(GL_KEEP,GL_DECR,GL_DECR);

	glDepthFunc(GL_NEVER);
}

void LightRendererGL::PopMask_End(){

	m_MaskStack--;
	glStencilFunc(GL_EQUAL,m_MaskStack,-1);
	glStencilOp(GL_KEEP,GL_KEEP,GL_KEEP);

	glDepthFunc(GL_ALWAYS);

	if(m_MaskStack == 0){
		glDisable(GL_STENCIL_TEST);
		glDisable(GL_DEPTH_TEST);
	}
}



void LightRendererGL::refresh_CXForm_Uniform()
{
	if(m_CXFormStack.size() > 0 ){
		CXFORM& ref = m_CXFormStack.back();

		m_Shader.setUniformLocationWith1i(u_use_cxform, 1);
		m_Shader.setUniformLocationWith4fv(u_cxform_mult_color, (GLfloat*) &(ref._MultR), 1);
		m_Shader.setUniformLocationWith4fv(u_cxform_add_color, (GLfloat*) &(ref._AddR), 1);
	}
	else{
		m_Shader.setUniformLocationWith1i(u_use_cxform, 0);
	}
}


void LightRendererGL::pushCXFORM( const CXFORM& new_cxform, bool use_stack_transform ){
	if(m_CXFormStack.size() > 0){
		m_CXFormStack.push_back( use_stack_transform ? (new_cxform * m_CXFormStack.back()) : new_cxform );
	}
	else{
		m_CXFormStack.push_back( new_cxform );
	}

	refresh_CXForm_Uniform();
}

void LightRendererGL::popCXFORM(){
	m_CXFormStack.pop_back();

	refresh_CXForm_Uniform();
}
